package com.sparrow.common.aoi.impl.crosslinked;

import com.sparrow.common.aoi.AbstractAOISystem;
import com.sparrow.common.aoi.domain.AOIEntity;
import com.sparrow.common.aoi.domain.MapData;
import com.sparrow.common.aoi.impl.ninegrid.AOIMap;
import com.sparrow.common.aoi.listener.AOIListener;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * 十字链表aoi 实现
 * 维护x,y 轴两个有序链表
 * 先后加入 (1,2) (3,4) (5,6)
 * x -> 1,3,5
 * y -> 2,4,6
 */
public class CrossLinkedAOISystem extends AbstractAOISystem {
    /**
     * 半径
     */
    private int radius = 5; // fixed AOI radius for now

    public void setRadius(int radius) {
        this.radius = radius;
    }

    public int getRadius() {
        return radius;
    }

    @Override
    public void addAOIEntity(AOIMap map, AOIEntity entity, int x, int y) {
        entity.setAOIX(x);
        entity.setAOIY(y);
        MapData mapData = map.getMapData();
        CrossLinkedNode node = new CrossLinkedNode(entity, entity.getAOIX(), entity.getAOIY());
        ((CrossLinkedAOIEntity)entity).setNode(node);
        //插入x轴
        insertNodeX(mapData, node);
        //插入y轴
        insertNodeY(mapData, node);

        Collection<AOIEntity> newView = queryAOIEntity(map, entity.getAOIX(), entity.getAOIX());
        for (AOIEntity e : newView) {
            if (e.equals(entity)) {
                continue;
            }
            for (AOIListener l : listeners) {
                l.onEntityEnterView(map, e, entity);
            }
        }
    }

    @Override
    public void updateAOIEntityPosition(AOIMap map, AOIEntity entity, int x, int y) {
        MapData mapData = map.getMapData();

        Collection<AOIEntity> oldView = queryAOIEntity(map, entity.getAOIX(), entity.getAOIY());

        CrossLinkedNode node = ((CrossLinkedAOIEntity)entity).getNode();
        removeNodeX(mapData, node);
        removeNodeY(mapData, node);
        node.x = x;
        node.y = y;
        entity.setAOIX(x);
        entity.setAOIY(y);
        insertNodeX(mapData, node);
        insertNodeY(mapData, node);

        Collection<AOIEntity> newView = queryAOIEntity(map, x, y);

        for (AOIEntity e : newView) {
            if (e.equals(entity)) {
                continue;
            }
            if (!oldView.contains(e)) {
                for (AOIListener l : listeners) {
                    l.onEntityEnterView(map, entity, e);
                    l.onEntityEnterView(map, e, entity);
                }
            } else {
                for (AOIListener l : listeners) {
                    l.onEntityPositionChange(map, e, entity);
                }
            }
        }
        for (AOIEntity e : oldView) {
            if (e.equals(entity)) {
                continue;
            }
            if (!newView.contains(e)) {
                for (AOIListener l : listeners) {
                    l.onEntityLeaveView(map, e, entity);
                    l.onEntityLeaveView(map, entity, e);
                }
            }
        }
    }

    @Override
    public Collection<AOIEntity> queryAOIEntity(AOIMap map, int x, int y) {
        MapData mapData = map.getMapData();
        List<AOIEntity> result = new ArrayList<>();
        CrossLinkedNode cur = mapData.xHead;
        //从 xHead 开始，跳过 x < x - radius 的节点。
        while (cur != null && cur.x < x - radius) {
            cur = cur.nextX;
        }
        //处理 [x - radius, x + radius]
        while (cur != null && cur.x <= x + radius) {
            if (Math.abs(cur.y - y) <= radius) {
                result.add(cur.entity);
            }
            cur = cur.nextX;
        }
        return result;
    }

    @Override
    public void removeAOIEntity(AOIMap map, AOIEntity entity) {
        MapData mapData = map.getMapData();
        CrossLinkedNode node = ((CrossLinkedAOIEntity)entity).getNode();
        removeNodeX(mapData, node);
        removeNodeY(mapData, node);

        int x = entity.getAOIX();
        int y = entity.getAOIY();
        Collection<AOIEntity> view = queryAOIEntity(map, x, y);
        for (AOIEntity e : view) {
            for (AOIListener l : listeners) {
                l.onEntityLeaveView(mapData, entity, e);
            }
        }
    }

    /**
     * 插入到x轴
     * @param mapData
     * @param node
     */
    private void insertNodeX(MapData mapData, CrossLinkedNode node) {
        CrossLinkedNode head = mapData.getXHead();
        if (head == null || node.x < head.x) {
            node.nextX = head;
            if (head != null) {
                head.prevX = node;
            }
            mapData.setXHead(node);
            return;
        }
        CrossLinkedNode cur = head;
        while (cur.nextX != null && cur.nextX.x < node.x) {
            cur = cur.nextX;
        }
        node.nextX = cur.nextX;
        if (cur.nextX != null) cur.nextX.prevX = node;
        cur.nextX = node;
        node.prevX = cur;
    }

    /**
     * 插入到y轴
     * @param mapData
     * @param node
     */
    private void insertNodeY(MapData mapData, CrossLinkedNode node) {
        CrossLinkedNode head = mapData.getYHead();
        if (head == null || node.y < head.y) {
            node.nextY = head;
            if (head != null) head.prevY = node;
            mapData.setYHead(node);
            return;
        }
        CrossLinkedNode cur = head;
        while (cur.nextY != null && cur.nextY.y < node.y) {
            cur = cur.nextY;
        }
        node.nextY = cur.nextY;
        if (cur.nextY != null) cur.nextY.prevY = node;
        cur.nextY = node;
        node.prevY = cur;
    }

    private void removeNodeX(MapData mapData, CrossLinkedNode node) {
        if (node.prevX != null) node.prevX.nextX = node.nextX;
        else mapData.setXHead(node.nextX);
        if (node.nextX != null) node.nextX.prevX = node.prevX;
        node.prevX = node.nextX = null;
    }

    private void removeNodeY(MapData mapData, CrossLinkedNode node) {
        if (node.prevY != null) node.prevY.nextY = node.nextY;
        else mapData.setYHead(node.nextY);
        if (node.nextY != null) node.nextY.prevY = node.prevY;
        node.prevY = node.nextY = null;
    }


    public static class CrossLinkedNode {
        public AOIEntity entity;
        public int x, y;
        public CrossLinkedNode prevX, nextX;
        public CrossLinkedNode prevY, nextY;

        public CrossLinkedNode(AOIEntity entity, int x, int y) {
            this.entity = entity;
            this.x = x;
            this.y = y;
        }
    }
}
